#pragma once

#include <memory>
#include <type_traits>
#include <string>
#include <zenox/glm.hpp>
#include <zenoui/Enums.hpp>

namespace zenoui {

struct Widget;

struct Property {
    Property &operator=(Property &&) = delete;
    virtual ~Property() = default;

    virtual void get_or_set(Widget *to_widget) = 0; // will be automatically implemented by PropertyCRTP
};

template <class DerivedProperty, class ValueType>
struct PropertyCRTP : Property {
    // when want to set an property on widget, m_value is initially non-null; when want to get, m_value is initially null
    // after complete set property, m_value should becomes null; after complete get proeprty, m_value becomes non-null
    std::unique_ptr<ValueType> m_value;

    using value_type = ValueType;

    PropertyCRTP() = default;

    ValueType value() { // get property value (call once), used in fetching argument value during property get (Widget::property)
        static_assert(std::is_base_of_v<PropertyCRTP, DerivedProperty>);
        auto ret = ValueType();
        if (m_value)
            ret = *m_value;
        m_value = nullptr;
        return ret;
    }

    void value(ValueType val) { // set property value, used in returning property value during property get (Widget::property)
        static_assert(std::is_base_of_v<PropertyCRTP, DerivedProperty>);
        m_value = std::make_unique<ValueType>(std::move(val));
    }

    bool empty() const { // on get, empty means the widget doesn't have this proeprty; on set, not empty means this property is read-only
        return m_value == nullptr;
    }

    bool has_value() const { // inverted logic of empty()
        return m_value != nullptr;
    }

    void by_ref(ValueType &val_ref) { // automatically decides get/set a class member as property by reference
        if (empty()) {
            value(val_ref); // property get from widget
        } else {
            val_ref = value(); // property set into widget
        }
    }

    void get_or_set(Widget *to_widget) override; // implemented later in Widget.hpp

    static bool set(Widget *widget, ValueType value) { // set the 'Derived' property of widget to 'value', return true on success
        DerivedProperty prop;
        prop.value(std::move(value));
        prop.get_or_set(widget);
        return prop.empty();
    }

    static ValueType get(Widget *widget, bool *ok = nullptr) { // get the 'Derived' property of widget, set (*ok) to true on success
        DerivedProperty prop;
        prop.get_or_set(widget);
        if (ok)
            *ok = prop.has_value();
        return prop.value(); // will return ValueType() default-constructed value on failure (e.g. 0 for int, "" for std::string)
    }
};

// below is for EmptyWidget:

struct BackgroundColorProperty : PropertyCRTP<BackgroundColorProperty, glm::vec4> {
};

// below is for LabelWidget:

struct FontNameProperty : PropertyCRTP<FontNameProperty, std::string> {
};

struct FontSizeProperty : PropertyCRTP<FontSizeProperty, float> {
};

struct FontColorProperty : PropertyCRTP<FontColorProperty, glm::vec4> {
};

struct TextProperty : PropertyCRTP<TextProperty, std::u32string> {
};

struct TextAlignmentProperty : PropertyCRTP<TextAlignmentProperty, TextAlignment> {
};

#define ZENO_FOREACH_PROPERTY_TYPE(_F) \
    /* X-macro pattern */ \
    _F(BackgroundColorProperty) \
    _F(FontColorProperty) \
    _F(FontNameProperty) \
    _F(FontSizeProperty) \
    _F(TextProperty) \
    _F(TextAlignmentProperty) \
    /* end of X-macro */

}
